#include "sock.h"
#include <fmt/core.h>
#include <ranges>

namespace rv = ::std::ranges::views;

namespace wilson::net
{
    socket::socket(ipv4_address a, in_port_t port)
        : s_ipaddr{ a }, s_port{ port }, 
          s_sockfd{ sys::fd_guard{ ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) } }
    {
    }

    socket::socket(ipv6_address a, in_port_t port)
        : s_ipaddr{ a }, s_port{ port }, 
          s_sockfd{ sys::fd_guard{ ::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP) }}
    {
    }

    ::std::string socket::to_string() const
    {
        // v4
        if (s_ipaddr.index() == 0) 
        {
            return fmt::format("[{}:{}]", 
                get<ipv4_address>(s_ipaddr).to_string(), 
                s_port
            );
        }
        else 
        {
            return fmt::format("[{}:{}]", 
                get<ipv6_address>(s_ipaddr).to_string(), 
                s_port
            );
        }
    }

    static sockaddr_in
    to_posix_sockaddr(ipv4_address addr, in_port_t port)
    {
        sockaddr_in ret{};

        ret.sin_family      = AF_INET;
        ret.sin_port        = htons(port);
        ret.sin_addr.s_addr = addr.to_uint32();
        
        return ret;
    }

    static sockaddr_in6 
    to_posix_sockaddr(const ipv6_address& addr, in_port_t port)
    {
        sockaddr_in6 ret{};
        ret.sin6_family = AF_INET6;
        ret.sin6_port = htons(port);

        uint32_t temp_data[4];
        memcpy(temp_data, addr.data(), sizeof(temp_data));
        for (auto& each_seg : temp_data)
        {
            each_seg = htonl(each_seg);
        }
        memcpy(ret.sin6_addr.s6_addr, temp_data, sizeof(temp_data));

        return ret;
    }

    void socket::posix_sockaddr(sockaddr* sock, socklen_t len)
    {
        if (posix_socklen() > len) [[unlikely]]
            throw ::std::logic_error{ "ipaddress len not equal!" };
        memset(sock, 0, len);

        // ipv4
        if (s_ipaddr.index() == 0)
        {
            *reinterpret_cast<sockaddr_in*>(sock) = 
                to_posix_sockaddr(get<ipv4_address>(s_ipaddr), s_port);
        }

        // ipv6
        else
        {
            *reinterpret_cast<sockaddr_in6*>(sock) = 
                to_posix_sockaddr(get<ipv6_address>(s_ipaddr), s_port);
        }
    }
    
    void socket::bind_as_tcp()
    {
        sockaddr_in posix_sock = to_posix_sockaddr(
            get<ipv4_address>(s_ipaddr), s_port
        );

        int err = ::bind(s_sockfd, 
            reinterpret_cast<sockaddr*>(&posix_sock), 
            sizeof(posix_sock)
        );

        if (err == -1) [[unlikely]]
        {
            s_sockfd.close();
            throw ::std::runtime_error{ ::strerror(errno) };
        }

        s_type = static_cast<type_t>(SOCK_STREAM);
    }

    void socket::bind_as_udp()
    {
        sockaddr_in posix_sock = to_posix_sockaddr(
            get<ipv4_address>(s_ipaddr), s_port
        );

        int err = ::bind(s_sockfd, 
            reinterpret_cast<sockaddr*>(&posix_sock), sizeof(posix_sock)
        );

        if (err == -1) [[unlikely]]
        {
            s_sockfd.close();
            throw ::std::runtime_error{ ::strerror(errno) };
        }

        s_type = static_cast<type_t>(SOCK_DGRAM);
    }

    void socket::reuse_addr()
    {
        int val = 1;
        ::setsockopt(fd(), SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
    }

    void socket::reuse_port()
    {
        int val = 1;
        ::setsockopt(fd(), SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
    }

}
